<template>
	<n-upload
		accept="*"
		:max="1"
		@before-upload="handleFileUpload"
		class="mb-4"
	>
		<n-button>选择文件</n-button>
	</n-upload>

	<div v-if="fileData" class="border rounded-lg overflow-hidden bg-white shadow-sm">
		<!-- 工具栏 -->
		<div class="flex items-center p-2 border-b bg-gray-50">
			<n-input
				v-model:value="searchTerm"
				placeholder="搜索..."
				class="mr-2 w-64"
				@keyup.enter="handleSearch"
			/>
			<n-select
				v-model:value="searchType"
				:options="searchOptions"
				class="mr-2 w-32"
			/>
			<n-button @click="handleSearch" class="mr-2">搜索</n-button>
			<div class="text-gray-500 text-sm">
				文件大小: {{ fileSize }} bytes
			</div>
		</div>

		<!-- 十六进制查看器主体 -->
		<n-scrollbar x-scrollable style="height: 70vh">
			<div class="flex font-mono text-sm relative" :style="{ height: totalHeight + 'px' }">
				<!-- 地址列 -->
				<div class="sticky left-0 bg-white z-10 pr-4 text-gray-500 border-r">
					<div
						v-for="row in visibleRows"
						:key="row.index"
						class="h-6 flex items-center justify-end pr-2"
						:class="{ 'bg-blue-100': highlightedRow === row.index }"
						@click="highlightedRow = row.index"
					>
						{{ row.address }}
					</div>
				</div>

				<!-- 十六进制列 -->
				<div class="px-4 border-r">
					<div
						v-for="row in visibleRows"
						:key="row.index"
						class="h-6 flex items-center"
						:class="{ 'bg-blue-100': highlightedRow === row.index }"
					>
            <span
				v-for="(byte, i) in row.hexBytes"
				:key="i"
				class="px-1"
				:class="{ 'text-red-500 font-bold': isHighlightedByte(row.index, i) }"
			>
              {{ byte }}
            </span>
					</div>
				</div>

				<!-- ASCII列 -->
				<div class="px-4 sticky right-0 bg-white border-l">
					<div
						v-for="row in visibleRows"
						:key="row.index"
						class="h-6 flex items-center"
						:class="{ 'bg-blue-100': highlightedRow === row.index }"
					>
						{{ row.ascii }}
					</div>
				</div>
			</div>
		</n-scrollbar>
	</div>
</template>

<script setup>
import {ref, computed, watchEffect} from 'vue'
import {NUpload, NButton, NScrollbar, NInput, NSelect} from 'naive-ui'

const ROWS_PER_PAGE = 100
const BYTES_PER_ROW = 16
const LINE_HEIGHT = 24 // px

// 响应式数据
const fileData = ref(null)
const highlightedRow = ref(-1)
const searchTerm = ref('')
const searchType = ref('hex')
const scrollTop = ref(0)
const containerHeight = ref(0)
const searchResults = ref([])
const currentSearchIndex = ref(-1)

// 搜索选项
const searchOptions = [
	{label: '十六进制', value: 'hex'},
	{label: 'ASCII', value: 'ascii'}
]

// 计算属性
const fileSize = computed(() => fileData.value?.length || 0)
const totalRows = computed(() => Math.ceil(fileData.value?.length / BYTES_PER_ROW) || 0)
const totalHeight = computed(() => totalRows.value * LINE_HEIGHT)

// 可见行计算
const visibleRows = computed(() => {
	if (!fileData.value) return []

	const start = Math.floor(scrollTop.value / LINE_HEIGHT)
	const end = start + Math.ceil(containerHeight.value / LINE_HEIGHT) + 2

	return Array.from({length: end - start}, (_, i) => {
		const rowIndex = start + i
		return getRowData(rowIndex)
	}).filter(row => row)
})

// 获取行数据
function getRowData(rowIndex) {
	if (rowIndex >= totalRows.value) return null

	const start = rowIndex * BYTES_PER_ROW
	const end = start + BYTES_PER_ROW
	const bytes = fileData.value.slice(start, end)

	return {
		index: rowIndex,
		address: (start).toString(16).padStart(8, '0').toUpperCase(),
		hexBytes: formatHexBytes(bytes),
		ascii: formatAscii(bytes)
	}
}

// 格式化十六进制字节
function formatHexBytes(bytes) {
	return Array.from(bytes)
		.map(b => b.toString(16).padStart(2, '0').toUpperCase())
		.join(' ')
		.match(/(.{1,24})/g)[0].split(' ') // 每两个字符分组
}

// 格式化ASCII
function formatAscii(bytes) {
	return Array.from(bytes)
		.map(b => {
			const char = String.fromCharCode(b)
			return char.match(/[\x20-\x7E]/) ? char : '.'
		})
		.join('')
}

// 处理文件上传
async function handleFileUpload({file}) {
	const reader = new FileReader()
	reader.onload = e => {
		const arrayBuffer = e.target.result
		fileData.value = new Uint8Array(arrayBuffer)
	}
	reader.readAsArrayBuffer(file.file)
}

// 搜索处理
function handleSearch() {
	if (!searchTerm.value || !fileData.value) return

	const searchValue = searchType.value === 'hex'
		? searchTerm.value.replace(/\s/g, '').toLowerCase()
		: searchTerm.value

	searchResults.value = []

	for (let i = 0; i < totalRows.value; i++) {
		const row = getRowData(i)
		const rowHex = row.hexBytes.join('')
		const rowAscii = row.ascii.toLowerCase()

		if (searchType.value === 'hex' && rowHex.includes(searchValue)) {
			searchResults.value.push(i)
		}
		if (searchType.value === 'ascii' && rowAscii.includes(searchValue.toLowerCase())) {
			searchResults.value.push(i)
		}
	}

	if (searchResults.value.length > 0) {
		currentSearchIndex.value = 0
		highlightedRow.value = searchResults.value[0]
		// 这里可以添加滚动到指定行的逻辑
	}
}

// 判断字节是否高亮
function isHighlightedByte(rowIndex, byteIndex) {
	if (currentSearchIndex.value === -1) return false
	const searchRow = searchResults.value[currentSearchIndex.value]
	return searchRow === rowIndex
}

// 容器高度侦听
watchEffect(() => {
	const container = document.querySelector('.n-scrollbar-content')
	if (container) {
		const observer = new ResizeObserver(entries => {
			containerHeight.value = entries[0].contentRect.height
		})
		observer.observe(container)
		return () => observer.disconnect()
	}
})

// 滚动事件处理
watchEffect(() => {
	const scrollContainer = document.querySelector('.n-scrollbar-container')
	if (scrollContainer) {
		const handler = () => {
			scrollTop.value = scrollContainer.scrollTop
		}
		scrollContainer.addEventListener('scroll', handler)
		return () => scrollContainer.removeEventListener('scroll', handler)
	}
})
</script>

<style>
.n-scrollbar-container {
	@apply h-full;
}

/* 优化滚动条样式 */
.n-scrollbar .n-scrollbar-rail {
	@apply bg-gray-100;
}

.n-scrollbar .n-scrollbar-rail__scrollbar {
	@apply bg-gray-300 hover:bg-gray-400;
}
</style>